package reflection;

import java.util.Stack;

import colour.Colour;
import intersection.GroupIntersection;

import primitives.AbstractShape;

import primitives.World3D;
import ray.Ray;
import vector.Vector;

/**
 * This class is used for dealing with reflection. It contains calculating two
 * important values, so it could produce a new reflected ray. author Yuting Wu
 * 
 * @author Kan Lin
 * @author Bing Dong
 * 
 */
public class ReflectionCalculation {
	private Vector pOriHitPoint;
	private Vector vOriHitPoint;
	private Ray rayInput;
	private World3D world;

	// the reflection value of each hitpoint during reflection
	private Stack<Double> tempRefNum = new Stack<Double>();
	// the colour value of each hitpoint during reflection
	private Stack<Colour> tempColor = new Stack<Colour>();

	/**
	 * @param pHitPoint
	 * @param vHitPoint
	 * @param rayInput
	 * @param world
	 */
	public ReflectionCalculation(Vector pHitPoint, Vector vHitPoint,
			Ray rayInput, World3D world) {
		super();
		this.pOriHitPoint = pHitPoint;
		this.vOriHitPoint = vHitPoint;
		this.rayInput = rayInput;
		this.world = world;

		/*
		 * this.sphereGroup = sphereGroup; this.planeGroup = planeGroup;
		 * this.triGroup = triGroup;
		 */
	}

	/**
	 * Create a new ray based on the ray input and hitpoint position and normal
	 * 
	 * @return a new ray
	 */
	private Ray produceRay(final Vector pIntersectionPoint,
			final Vector vIntersectionPoint, final Ray rayInput) {

		// calculate the location of reflected ray
		Vector pT0 = pIntersectionPoint
				.add(vIntersectionPoint.multiply(0.0001));
		// calculate direction of reflected ray
		final double temp1 = vIntersectionPoint.dotProduct(rayInput
				.getDesiredRay());
		final double temp2 = 2 * temp1;
		final Vector temp3 = vIntersectionPoint.multiply(temp2);
		Vector vTd = rayInput.getDesiredRay().subtract(temp3);

		return new Ray(pT0, vTd);
	}

	/**
	 * Calculation the colour of the first reflected hitpoint after the whole
	 * process of the reflection
	 * 
	 * @param reflectionDepth
	 * @return
	 */
	public Colour calReflectValue(final int reflectionDepth) {

		Ray rayReflection = produceRay(pOriHitPoint, vOriHitPoint, rayInput);
		for (int i = reflectionDepth; i > 0; i--) {
			if (rayReflection == null) {
				break;
			} else {
				if (calPixelDetails(rayReflection)) {
					rayReflection = produceRay(pOriHitPoint, vOriHitPoint,
							rayReflection);
				} else {
					break;
				}
			}
		}
		// Calculate the colour of reflection
		Colour tempCCalculate = new Colour(0, 0, 0);

		while (!tempColor.isEmpty()) {
			double tempRN1 = tempRefNum.pop();
			Colour tempCC = tempColor.pop();
			tempCCalculate = tempCC.add(tempCCalculate.multiply(tempRN1));
		}

		return tempCCalculate;

	}

	/**
	 * Calculate the value of RGB , reflection and position of reflection point
	 * for the input ray.
	 * 
	 * @param ray
	 * @return false means no hit
	 */
	private boolean calPixelDetails(final Ray ray) {

		// calculate the nearest point
		final GroupIntersection gHit = new GroupIntersection(world);

		boolean flag = gHit.calMutiIntersection(ray);

		if (flag) {

			AbstractShape obj = gHit.getObjHit();
			double reflect = obj.getReflect();
			if (reflect - 0.0 >= 0.0001) {
				// get the position and normal vector
				pOriHitPoint = gHit.getpHitpoint();
				vOriHitPoint = gHit.getvHitpoint();
				// record the reflect number
				tempRefNum.push(obj.getReflect());
				// calculate the colour
				this.tempColor.push(obj.getColorAt(pOriHitPoint, vOriHitPoint));
			} else {
				flag = false;
			}

		}
		return flag;

	}

}
